package com.iwhalecloud.citybrain.flow.platform.manager.application.common.filter;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.apache.commons.io.IOUtils;

@Component
public class RequestReplaceFilter extends OncePerRequestFilter {


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (!(request instanceof MyServletRequestWrapper)) {
            request = new MyServletRequestWrapper(request);
        }
        filterChain.doFilter(request, response);
    }

    public class MyServletRequestWrapper extends HttpServletRequestWrapper {

        private final byte[] body;

        public MyServletRequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            body = IOUtils.toByteArray(super.getInputStream());
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return new RequestBodyCachingInputStream(body);
        }

        private class RequestBodyCachingInputStream extends ServletInputStream {
            private byte[] body;
            private int lastIndexRetrieved = -1;
            private ReadListener listener;

            public RequestBodyCachingInputStream(byte[] body) {
                this.body = body;
            }

            @Override
            public int read() throws IOException {
                if (isFinished()) {
                    return -1;
                }
                int i = body[lastIndexRetrieved + 1];
                lastIndexRetrieved++;
                if (isFinished() && listener != null) {
                    try {
                        listener.onAllDataRead();
                    } catch (IOException e) {
                        listener.onError(e);
                        throw e;
                    }
                }
                return i;
            }

            @Override
            public boolean isFinished() {
                return lastIndexRetrieved == body.length - 1;
            }

            @Override
            public boolean isReady() {
                return isFinished();
            }

            @Override
            public void setReadListener(ReadListener listener) {
                if (listener == null) {
                    throw new IllegalArgumentException("listener cann not be null");
                }
                if (this.listener != null) {
                    throw new IllegalArgumentException("listener has been set");
                }
                this.listener = listener;
                if (!isFinished()) {
                    try {
                        listener.onAllDataRead();
                    } catch (IOException e) {
                        listener.onError(e);
                    }
                } else {
                    try {
                        listener.onAllDataRead();
                    } catch (IOException e) {
                        listener.onError(e);
                    }
                }
            }

            @Override
            public int available() throws IOException {
                return body.length - lastIndexRetrieved - 1;
            }

            @Override
            public void close() throws IOException {
                lastIndexRetrieved = body.length - 1;
                body = null;
            }
        }
    }

}
